import com.google.gson.Gson;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;

/**
 * Created by IntelliJ IDEA.
 * User: shameera
 * Date: 1/22/12
 * Time: 8:58 PM
 * To change this template use File | Settings | File Templates.
 */
public class MainInvoker {


//    private Reader reader;
    private InputStream in;
//    private Writer writer;
    private OutputStream out;
    private String serviceName;
    private String operation;
    private JsonReader reader;
    private JsonWriter writer;
    private boolean release = false;
    Gson gson = new Gson();


    public void invokeService(InputStream in , OutputStream out){ //Reader reader , Writer writer){

        try {
            reader = new JsonReader(new InputStreamReader(in , "UTF-8"));
            writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return ;
        }

        getServiceAndOperationNames();

        if (serviceName != null || serviceName != "" || operation != null || operation != "") {
            try {
                Class   className = Class.forName(serviceName);
                Object  serivceInstance = className.newInstance();
                Object[] methodParam;
                int paramCount=0;
                boolean continue_ = true;
                boolean param_array=false;
                Method[] allmethods =  className.getDeclaredMethods();
                Method method = getOpMethod(operation, allmethods);

//                        Type[] paraTypes = method.getGenericParameterTypes();
                Class[] paramClasses = method.getParameterTypes();
//                        for(Class tem:paramClasses){
//                            System.out.println(tem.getName());
//                        }
                paramCount = paramClasses.length;
                JsonToken token = reader.peek();
                methodParam = new Object[paramCount];
                if(paramCount==0){

                }else if(paramCount>0){
                    if(paramCount!=1){
                        reader.beginArray();
                        param_array=true;
                        token = reader.peek();
                    }
                    int i = 0;
                    for (Class tempClass : paramClasses) {
                        if (TypeMatcher.isStringType(tempClass)) {
                            methodParam[i] = reader.nextString();
                            i++;
                            token = reader.peek();
                        }else if(TypeMatcher.isBooleanType(tempClass)){
                            methodParam[i] = reader.nextBoolean();
                            i++;
                            token = reader.peek();
                        }else if(TypeMatcher.isDoubleType(tempClass)){
                            methodParam[i] = reader.nextDouble();
                            i++;
                            token = reader.peek();
                        }else if(TypeMatcher.isIntType(tempClass)){
                            methodParam[i] = reader.nextInt();
                            i++;
                            token = reader.peek();
                        }else if(TypeMatcher.isLongType(tempClass)){
                            methodParam[i] = reader.nextLong();
                            i++;
                            token = reader.peek();
                        }else if (TypeMatcher.isObjectType(tempClass)) {
                            String objtypetoString = tempClass.toString();
                            if (token.equals(JsonToken.BEGIN_OBJECT)) {
                                methodParam[i] = gson.fromJson(reader, tempClass);

                            }else if (token.equals(JsonToken.NULL)) {
                                methodParam[i] = new Object();
                                reader.nextNull();
                            }else{
                                //TODO- check for Enum , Array  and Collection types and handle them
                            }
                            token = reader.peek();
                            i++;
                        }
                    }
                }
//                        JsonToken token = reader.peek();
//                        if(paramCount>1){
//                            reader.beginArray();
//                            token = reader.peek();
//                        }
                Object retObj = method.invoke(serivceInstance, methodParam);

                writeToOutput(retObj, method, writer);

                if (retObj != null) {
                    System.out.println(retObj.toString());
                }else{
                    System.out.println("return type is void :) ");
                }

            } catch (ClassNotFoundException e) {
                System.out.println("given quilified service name is wrong");
            } catch (InstantiationException e) {
                System.out.println(e.toString());
            } catch (IllegalAccessException e) {
                System.out.println(e.toString());
            } catch (InvocationTargetException e) {
                System.out.println(e.toString());
            } catch (IOException e) {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            }
        }
    }


    private Method getOpMethod(String methodName, Method[] methodSet) {
        for (Method method : methodSet) {
            String mName = method.getName();
            if (mName.equals(methodName)) {
                return method;
            }
        }
        return null;
    }


    private boolean writeToOutput(Object retObj , Method opMethod ,JsonWriter writer) throws IOException {

        Class returnClass = opMethod.getReturnType();
        writer.beginObject();
        writer.name("return");
//        Type returnType = opMethod.getGenericReturnType();
        //TODO- check whether return type is Array , Enum or Collection type , if so handle it
        gson.toJson(retObj , returnClass ,writer);
        writer.endObject();
        writer.flush();
        writer.close();
//        if (TypeMatcher.isStringType(returnClass)) {
//        }
        return false;
    }

    private void getServiceAndOperationNames() {

        try {
            System.out.println(reader.toString());
            reader.setLenient(true);
            JsonToken token_ = reader.peek();
            while(!release){
                switch (token_) {
                    case NAME:
                        String name = reader.nextName();
                        if(serviceName==null){
                            serviceName = name;
                            token_ = reader.peek();
                        }else {
                            operation = name;
                            release = true;
                        }
//                        writer.name(name);
                        break;
//                    case STRING:
//                        String s = reader.nextString();
//                        token_ =reader.peek();
//                        System.out.println(s);

                    case BEGIN_OBJECT:
                        reader.beginObject();
                        token_ = reader.peek();
                        break;

                    default:
                        return;
                }
            }

        } catch (IOException e) {
            System.out.println("json input string is not correct");
        }

        return ;

    }


    public static void main(String[] args) throws IOException {
        InputStream IS = MainInvoker.class.getResourceAsStream("/testIn.txt");
        OutputStream outIS = new FileOutputStream(new File("src/main/resources/testOut.txt"));
//        InputStream in = new FileInputStream(new File("/home/shameera/Desktop/test.txt"));
//        OutputStream out = new FileOutputStream(new File("/home/shameera/Desktop/testout.txt"));
        MainInvoker invoker = new MainInvoker();
        invoker.invokeService(IS , outIS);
    }
}


